/*
 * nlock.c
 *
 *  Created on: 21/05/2012
 *      Author: Idan
 */
#include <pthread.h>
#include <assert.h>
typedef enum{READ, WRITE} lock_type
typedef struct node_t {
	lock_type type;
	pthread_cond_t condition;
	pthread_t mayWriteId;
	int readers;
	node* next;
	int mayWriteFlag;
} node;

typedef struct lock_t {
	node* head;
	node* tail;
	int number_of_readers;
	int number_of_writers;
	pthread_mutex_t global_lock;
	bool
} lock;


void lock_init(lock* l){
	l->head = NULL;
	l->tail = NULL;
	pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_ERRORCHECK_NP);
	pthread_mutex_init(&lock,&attr);
	pthread_mutexattr_destroy(&attr);
}

static node* nodeInit(lock_type type){
	node* qElement = (node)malloc(sizeof(*qElement));
	qElement->type = type;
	pthread_cond_init(&(qElement->condition),NULL);
	qElement->readers = type==READ? 1:0;
	qElement->mayWriteFlag = 0;
	qElement->next = NULL;
	qElement->id = -1;
	return qElement;
}

void get_read_lock(lock* l){
	pthread_mutex_lock(&global_lock);
	if(!l->head){
			l->head = nodeInit(READ);
			l->tail = l->head;
	}
	else if(l->tail->type == READ){
		l->tail->readers++;
	}
	else{
		node newNode = nodeInit(READ);
		l->tail->next = newNode;
		l->tail = newNode;
	}
	while(l->number_of_writers > 0)
		pthread_cond_wait(&(l->tail->condition),&(l->global_lock));
	pthread_mutex_unlock(&global_lock);
}

void get_may_write_lock(lock *l){
	pthread_mutex_lock(&global_lock);
	if(!head){
			l->head = nodeInit(READ);
			l->head->mayWriteFlag = 1;
			l->head->readers = 0;
			l->head->id = pthread_self();
			l->tail = l->head;
	}
	else if(l->tail->type == READ &&(l->tail->mayWriteFlag == 0)){
		l->tail->mayWriteFlag = 1;
	}
	else{
		node newNode = nodeInit(READ);
		newNode->mayWriteFlag = 1;
		tail->next = newNode;
		tail = newNode;
	}
	while(l->number_of_writers > 0)
		pthread_cond_wait(&(l->tail->condition),&(l->global_lock));
	pthread_mutex_unlock(&global_lock);
}

void get_write_lock(lock* l){
	pthread_mutex_lock(&global_lock);
	if(!l->head){
			l->head = nodeInit(WRITE);
			l->tail = l->head;
	}
	node newNode = nodeInit(WRITE);
	l->tail->next = newNode;
	l->tail = newNode;
	while(l->number_of_readers > 0 || l->number_of_writers > 0)
		pthread_cond_wait(&(l->tail->condition),&(l->global_lock));
	pthread_mutex_unlock(&global_lock);
}

void upgrade_may_write_lock(lock* l){
	pthread_mutex_lock(&global_lock);
	assert(l->head->mayWriteId == pthread_self());
	node newNode = nodeInit(WRITE);
	newNode->next = l->head->next;
	l->head->next = newNode;
	while(l->number_of_readers > 0)
		pthread_cond_wait(&(l->tail->condition),&(l->global_lock));
	pthread_mutex_unlock(&global_lock);
}

void release_shared_lock(lock* l){
	pthread_mutex_lock(&global_lock);
	if(l->head->readers > 1){
		l->head->readers--;
	}
	else if(l->head->readers==1){
		node* temp = head;
		head = head->next;
		free(temp);
		l->number_of_readers--;
		pthread_cond_broadcast(&(l->head->condition));
	}
	pthread_mutex_unlock(&global_lock);
}

void release_exclusive_lock(lock* l){
	pthread_mutex_lock(&global_lock);
	node* temp = head;
	head = head->next;
	free(temp);
	l->number_of_writers--;
	pthread_cond_broadcast(&(l->head->condition));
	pthread_mutex_unlock(&global_lock);
}


